/*
 * \brief  Fork bomb to stress Genode
 * \author Christian Helmuth
 * \date   2007-08-16
 *
 * The better part of this code is derived from the original init
 * implementation by Norman.
 */

/*
 * Copyright (C) 2007-2009 Christian Helmuth
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */
/* #include <i9foundation.h> */
#include <base/env.h>
#include <base/child.h>
#include <base/sleep.h>
#include <base/service.h>
#include <base/snprintf.h>
#include <init/child.h>
#include <ram_session/connection.h>
#include <cpu_session/connection.h>
#include <rom_session/connection.h>
#include <cap_session/connection.h>
#include <timer_session/connection.h>

using namespace Genode;


/*
 * Our special Child implementation tracks ROM sessions and prevents children
 * from using the Timer service.
 */
class Bomb_child : public Genode::List<Bomb_child>::Element,
                   public Init::Child
{
	private:

		Session_capability _rom_session_cap;

	public:

		Bomb_child(const char            *name,
		           Dataspace_capability   elf_ds_cap,
		           Ram_session_capability ram_session_cap,
		           Cpu_session_capability cpu_session_cap,
		           Rom_session_capability rom_session_cap,
		           Cap_session           *cap_session,
		           Remote_service_pool   *remote_services)
		:
			Init::Child(name, elf_ds_cap, ram_session_cap, cpu_session_cap,
			            cap_session, remote_services, Dataspace_capability()),
			_rom_session_cap(rom_session_cap) { }

		~Bomb_child()
		{
			PDBG("called");
		}

		Session_capability rom_session_cap() { return _rom_session_cap; }

		Session_capability session(const char *service_name, const char *args)
		{
			/* be sure our children cannot request a timer session */
			if (strcmp("Timer", service_name) == 0)
				return Session_capability();

			/* the rest is in Init::Child */
			return Init::Child::session(service_name, args);
		}
};


/*
 * List of children
 *
 * Access to the children list from different threads
 * must be synchronized via the children lock.
 */
static Lock             _children_lock;
static List<Bomb_child> _children;


/**
 * Check if a program with the specified name already exists
 */
static bool child_name_exists(const char *name)
{
	Bomb_child *c = _children.first();

	for ( ; c; c = c->List<Bomb_child>::Element::next())
		if (strcmp(c->name(), name) == 0)
			return true;

	return false;
}


/**
 * Create a unique name based on the filename
 *
 * If a program with the filename as name already exists, we
 * add a counting number as suffix.
 */
static void get_unique_child_name(const char *filename, char *dst, size_t dst_len)
{
	Lock::Guard lock_guard(_children_lock);

	char buf[32];
	char suffix[8];
	suffix[0] = 0;

	for (int cnt = 1; true; cnt++) {

		/* build program name composed of filename and numeric suffix */
		snprintf(buf, sizeof(buf), "%s%s", filename, suffix);

		/* if such a program name does not exist yet, we are happy */
		if (!child_name_exists(buf)) {
			strncpy(dst, buf, dst_len);
			return;
		}

		/* increase number of suffix */
		snprintf(suffix, sizeof(suffix), ".%d", cnt + 1);
		
	}
}


/**
 * Start a child
 */
static int start_child(const char *filename, Remote_service_pool *remote_services,
                       Cap_session *cap_session, size_t ram_quota = 1024*1024)
{
	char unique_name[64];
	get_unique_child_name(filename, unique_name, sizeof(unique_name));

	/* lookup executable elf binary */
	Rom_connection rom(filename);
	Dataspace_capability file_cap = rom.dataspace();

	/* create ram session for child with some of our own quota */
	Ram_connection ram(unique_name);
	ram.ref_account(env()->ram_session_cap());
	env()->ram_session()->transfer_quota(ram.cap(), ram_quota);

	/* create cpu session for child */
	Cpu_connection cpu(unique_name);

	if (!ram.cap().valid() || !cpu.cap().valid()) {
		PWRN("ram or cpu session not valid");
		return -2;
	}

	/* do not close session when leaving the current scope */
	cpu.on_destruction(Cpu_connection::KEEP_OPEN);
	rom.on_destruction(Rom_connection::KEEP_OPEN);
	ram.on_destruction(Ram_connection::KEEP_OPEN);

	Bomb_child *c = new (env()->heap())
		Bomb_child(unique_name, file_cap, ram.cap(), cpu.cap(),
		           rom.cap(), cap_session, remote_services);
	c->finalize_construction();

	Lock::Guard lock_guard(_children_lock);
	_children.insert(c);
	return 0;
}


/**
 * Kill child
 */
static void exit_child(Bomb_child *child)
{
	Session_capability ram_session_cap = child->ram_session_cap(),
	                   cpu_session_cap = child->cpu_session_cap(),
	                   rom_session_cap = child->rom_session_cap();

	child->prepare_destruction();
	destroy(env()->heap(), child);

	env()->parent()->close(cpu_session_cap);
	env()->parent()->close(rom_session_cap);
	env()->parent()->close(ram_session_cap);
}


int main(int argc, char **argv)
{
	printf("--- bomb started ---\n");

	Remote_service_pool remote_services;

	/* connect to core's cap service used for creating parent capabilities */
	Cap_connection cap;

	/* request timer service - does not work if bomb is our parent */
	Timer::Connection timer;

	const long children = 2;
	const long demand   = 256 * 1024;

	unsigned long avail = env()->ram_session()->avail();
	long amount = (avail - demand) / children;
	if (amount < (children * demand)) {
		PDBG("I'm a leaf node.");
		sleep_forever();
	}

	while (1) {
		for (unsigned i = children; i; --i)
			start_child("bomb", &remote_services, &cap, amount);

		/* is init our parent? */
		if (!timer.cap().valid()) sleep_forever();

		timer.msleep(2000);
		PDBG("It's time to kill all my children...");

		while (1) {
			Bomb_child *c;

			_children_lock.lock();
			c = _children.first();
			if (c) _children.remove(c);
			_children_lock.unlock();

			if (c) exit_child(c);
			else break;
		}

		PDBG("Done.");
	}

	sleep_forever();
	return 0;
}
